commonlibsse_ng\rel\module/runtime.rs
1// C++ Original code
2// - https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/include/REL/Module.h
3// - load_segments, clear: https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/src/REL/Module.cpp
4// SPDX-FileCopyrightText: (C) 2018 Ryan-rsm-McKenzie
5// SPDX-License-Identifier: MIT
6//
7// SPDX-FileCopyrightText: (C) 2025 SARDONYX
8// SPDX-License-Identifier: Apache-2.0 OR MI
9
10//! Defines Runtime(e.g. `Runtime::Ae`) types.
11
12use crate::rel::version::Version;
13
14/// Defines Skyrim runtime versions.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum Runtime {
17 /// The Skyrim runtime is a post-Anniversary Edition Skyrim SE release (version 1.6.x and later).
18 Ae = 1,
19 /// The Skyrim runtime is a pre-Anniversary Edition Skyrim SE release (version 1.5.97 and prior).
20 Se = 1 << 1,
21 /// The Skyrim runtime is Skyrim VR.
22 Vr = 1 << 2,
23}
24
25impl Runtime {
26 /// Get the runtime from version.
27 ///
28 /// This function takes a `Version` object and returns the corresponding `Runtime` variant.
29 ///
30 /// The runtime is determined based on the version's `minor` numbers:
31 /// - `minor` 4 -> `Runtime::Vr` (Skyrim VR)
32 /// - `minor` 6 -> `Runtime::Ae` (Skyrim Anniversary Edition)
33 /// - Any other version is considered `Runtime::Se` (Skyrim Special Edition).
34 ///
35 /// If you want strictness, use `Runtime::from_version_strict`.
36 ///
37 /// # Example
38 ///
39 /// ```
40 /// use commonlibsse_ng::rel::module::Runtime;
41 /// use commonlibsse_ng::rel::version::Version;
42 ///
43 /// let version = Version::new(1, 5, 50, 0); // SE version
44 /// let runtime = Runtime::from_version(&version);
45 /// assert_eq!(runtime, Runtime::Se);
46 ///
47 /// let version = Version::new(1, 6, 317, 0); // AE version
48 /// let runtime = Runtime::from_version(&version);
49 /// assert_eq!(runtime, Runtime::Ae);
50 /// ```
51 ///
52 /// # Laxity of judgment.
53 /// This judgment is incorrectly determined to be Vr if the SE is 1.4.2.
54 ///
55 /// This method is useful under the following assumptions
56 /// - SE users are using the latest (1.5.97).
57 /// - The version update of this library has not caught up with the version of Skyrim, even though the version of Skyrim has been upgraded.
58 #[inline]
59 pub const fn from_version(version: &Version) -> Self {
60 match version.minor() {
61 4 => Self::Vr,
62 6 => Self::Ae,
63 _ => Self::Se,
64 }
65 }
66
67 /// Get the runtime from version, strictly matching predefined database versions.
68 ///
69 /// This function will only return a runtime if the version matches exactly one of the
70 /// predefined versions in the database.
71 ///
72 /// # Example
73 ///
74 /// ```
75 /// use commonlibsse_ng::rel::module::Runtime;
76 /// use commonlibsse_ng::rel::version::Version;
77 /// use commonlibsse_ng::skse::version::{RUNTIME_SSE_1_5_50, RUNTIME_SSE_1_6_317, RUNTIME_VR_1_4_15};
78 ///
79 /// // SE version within range
80 /// let runtime = Runtime::from_version_strict(&RUNTIME_SSE_1_5_50);
81 /// assert_eq!(runtime, Some(Runtime::Se));
82 ///
83 /// // AE version within range
84 /// let runtime = Runtime::from_version_strict(&RUNTIME_SSE_1_6_317);
85 /// assert_eq!(runtime, Some(Runtime::Ae));
86 ///
87 /// // VR version
88 /// let runtime = Runtime::from_version_strict(&RUNTIME_VR_1_4_15);
89 /// assert_eq!(runtime, Some(Runtime::Vr));
90 /// ```
91 pub const fn from_version_strict(version: &Version) -> Option<Self> {
92 use crate::skse::version::*;
93
94 // Match specific predefined version constants for SE, AE, and VR
95
96 Some(match *version {
97 // SE versions (1.1.47 to 1.5.97)
98 RUNTIME_SSE_1_1_47 | RUNTIME_SSE_1_1_51 | RUNTIME_SSE_1_2_36 | RUNTIME_SSE_1_2_39
99 | RUNTIME_SSE_1_3_5 | RUNTIME_SSE_1_3_9 | RUNTIME_SSE_1_4_2 | RUNTIME_SSE_1_5_3
100 | RUNTIME_SSE_1_5_16 | RUNTIME_SSE_1_5_23 | RUNTIME_SSE_1_5_39 | RUNTIME_SSE_1_5_50
101 | RUNTIME_SSE_1_5_53 | RUNTIME_SSE_1_5_62 | RUNTIME_SSE_1_5_73 | RUNTIME_SSE_1_5_80
102 | RUNTIME_SSE_1_5_97 => Self::Se,
103
104 // AE versions (1.6.0 to 1.6.1170)
105 RUNTIME_SSE_1_6_317 | RUNTIME_SSE_1_6_318 | RUNTIME_SSE_1_6_323
106 | RUNTIME_SSE_1_6_342 | RUNTIME_SSE_1_6_353 | RUNTIME_SSE_1_6_629
107 | RUNTIME_SSE_1_6_640 | RUNTIME_SSE_1_6_659 | RUNTIME_SSE_1_6_678
108 | RUNTIME_SSE_1_6_1130 | RUNTIME_SSE_1_6_1170 => Self::Ae,
109
110 // VR version (1.4.15)
111 RUNTIME_VR_1_4_15 => Self::Vr,
112 _ => return None,
113 })
114 }
115
116 /// Is the current Skyrim runtime the Anniversary Edition (AE)?
117 #[inline]
118 pub fn is_ae(&self) -> bool {
119 *self == Self::Ae
120 }
121
122 /// Is the current Skyrim runtime the Special Edition (SE).
123 #[inline]
124 pub fn is_se(&self) -> bool {
125 *self == Self::Se
126 }
127
128 /// Is the current Skyrim runtime the VR version?
129 #[inline]
130 pub fn is_vr(&self) -> bool {
131 *self == Self::Vr
132 }
133}
134
135/// Get `SkyrimSE.exe`/`SkyrimVR.exe` dir path from registry.
136///
137/// If got `None`, get path from registry.
138pub fn get_skyrim_dir(target_runtime: Runtime) -> Option<std::path::PathBuf> {
139 use std::ffi::OsString;
140 use std::os::windows::ffi::OsStringExt;
141 use std::path::PathBuf;
142 use windows::Win32::Foundation::ERROR_SUCCESS;
143 use windows::Win32::System::Registry::{HKEY_LOCAL_MACHINE, REG_ROUTINE_FLAGS, RegGetValueW};
144 use windows::core::h;
145
146 let sub_key = match target_runtime {
147 Runtime::Se | Runtime::Ae => h!(r"SOFTWARE\Bethesda Softworks\Skyrim Special Edition"),
148 Runtime::Vr => h!(r"SOFTWARE\Bethesda Softworks\Skyrim VR"),
149 };
150
151 const BUFFER_SIZE: usize = 4096; // Max NTFS path length
152 let mut value = vec![0_u16; BUFFER_SIZE];
153 let mut length = (BUFFER_SIZE * std::mem::size_of::<u16>()) as u32;
154
155 let status = unsafe {
156 RegGetValueW(
157 HKEY_LOCAL_MACHINE,
158 sub_key,
159 h!("Installed Path"),
160 REG_ROUTINE_FLAGS(0x20002),
161 None,
162 Some(value.as_mut_ptr().cast()),
163 Some(&mut length),
164 )
165 };
166
167 if status != ERROR_SUCCESS {
168 return None;
169 }
170
171 // Convert UTF-16 buffer to PathBuf
172 let path_str =
173 OsString::from_wide(&value[..(length as usize / 2)]).to_string_lossy().to_string();
174
175 Some(PathBuf::from(path_str.trim_end_matches('\0')))
176}
177
178/// Get `SkyrimSE.exe`/`SkyrimVR.exe` path from registry.
179///
180/// If got `None`, get path from registry.
181pub fn get_skyrim_exe_path(target_runtime: Runtime) -> Option<std::path::PathBuf> {
182 let mut install_path = get_skyrim_dir(target_runtime)?;
183 install_path.push(match target_runtime {
184 Runtime::Se | Runtime::Ae => "SkyrimSE.exe",
185 Runtime::Vr => "SkyrimVR.exe",
186 });
187 Some(install_path)
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use crate::skse::version::{RUNTIME_SSE_1_5_50, RUNTIME_SSE_1_6_317, RUNTIME_VR_1_4_15};
194
195 /// Tests the correct conversion from `Version` to `Runtime`.
196 /// This simulates real-world scenarios based on Skyrim's version numbers.
197 #[test]
198 fn test_runtime_enum() {
199 // Checking the enum values by their numeric representation.
200 assert_eq!(Runtime::Ae as u8, 1);
201 assert_eq!(Runtime::Se as u8, 2);
202 assert_eq!(Runtime::Vr as u8, 4);
203
204 assert_eq!(Runtime::from_version(&RUNTIME_SSE_1_5_50), Runtime::Se);
205 assert_eq!(Runtime::from_version(&RUNTIME_SSE_1_6_317), Runtime::Ae);
206 assert_eq!(Runtime::from_version(&RUNTIME_VR_1_4_15), Runtime::Vr);
207
208 let version_1_4_5 = Version::new(1, 4, 5, 0); // Unknown version (not recognized by rules)
209 assert_eq!(Runtime::from_version(&version_1_4_5), Runtime::Vr);
210 assert_eq!(Runtime::from_version_strict(&version_1_4_5), None);
211 }
212
213 #[ignore = "local only"]
214 #[test]
215 fn test_get_skyrim_exe_path() {
216 dbg!(get_skyrim_exe_path(Runtime::Se));
217 }
218}